package com.hax2max.control;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import android.content.Context;
import android.database.Cursor;
import android.util.Log;

import com.hax2max.R;
import com.hax2max.control.FriendStalkerException.IExternalClient;
import com.hax2max.control.FriendStalkerException.UserNotValid;
import com.hax2max.model.ExternalData;
import com.hax2max.model.FootPrint;
import com.hax2max.model.FullProfile;
import com.hax2max.model.Profile;

public class ProfileFactory {

	private static DbAdapter dbAdapter;
	private static CommunicationConnector cc;
	private static final String TAG = "ProfileFactory";
	private Date lastUpdated;
	public Context ctx;
	private WebServerClient wsClient;

	public ProfileFactory(Context contx) {
		wsClient =  WebServerClient.getInstance();
		dbAdapter = new DbAdapter(contx);
		dbAdapter.open();
		cc = CommunicationConnector.getInstance();
		ctx = contx;
	}
	
	/**
	 * Tries to login the user to the system.
	 * 
	 * @param username
	 * @param password
	 * @return the user profile
	 * @throws UserNotValid
	 */
	public Profile loginUser(String username, String password,
			boolean internetAvailable) throws UserNotValid {
		if (!dbAdapter.isOpen())
			dbAdapter.open();
		
		Profile userProfile = loginLocal(username, password);
		
		if (userProfile != null)
			lastUpdated = userProfile.lastUpdated;
		else
			lastUpdated = new Date(0);
		if (internetAvailable) {
			if (userProfile == null)
				dbAdapter.clean();
			userProfile = wsClient.loginUser(username, password);
			userProfile.lastUpdated = lastUpdated;
			dbAdapter.writeProfile(userProfile);
		}
		if (userProfile == null)
			throw new FriendStalkerException.UserNotValid(R.string.login_failed);

		userProfile.externalData = mapProfileExternal(userProfile);

		return userProfile;
	}

	private Profile loginLocal(String username, String password) {
		Cursor c = dbAdapter.login(username, password);
		if (c.getCount() > 0) {
			c.moveToFirst();
			Profile userProfile = this.createProfile(c);
			c.close();
			return userProfile;
		} else
			return null;
	}

	public void logOut() {
		cc.logOutClient();
		dbAdapter.close();
	}

	private ExternalData[] mapProfileExternal(Profile profile) {

		ExternalData[] edList = new ExternalData[ExternalData.applications.length];
		int i = 0;
		for (int applicationID : ExternalData.applications) {
			edList[i] = getExternalData(profile, applicationID, true);
			i++;
		}
		return edList;
	}

	/**
	 * 
	 * @param p
	 * @param id
	 * @param returnEmpty
	 *            true, return empty External data if none exists, if set false,
	 *            return null
	 * @return ExternalData object from database if none exists returns empty
	 *         External data or null (depended on boolean returnEmpty)
	 */
	public ExternalData getExternalData(Profile profile, int applicationID,
			boolean returnEmpty) {
		boolean noExternalDataFound = false;

		ExternalData ed = new ExternalData();
		ed.application = applicationID;
		ed.errorMsg = "";
		ed.latestUpdate = new Date(0);
		ed.username = profile.username;
		ed.statusText = "";
		ed.success = true;
		ed.userId = "";

		Cursor c = dbAdapter.getExternal(profile.username, applicationID);
		if (c.moveToNext()) {
			noExternalDataFound = false;
			ed.statusText = c.getString((c
					.getColumnIndex(ExternalData.statusTextColumn)));
			ed.password = c.getString((c
					.getColumnIndex(ExternalData.passwordColumn)));
			ed.login = c.getString((c
					.getColumnIndex(ExternalData.loginColumn)));
		}

		else {
			noExternalDataFound = true;
			ed.password = "";
			ed.login = "";
		}

		c.close();
		if (!returnEmpty && noExternalDataFound)
			return null;
		else
			return ed;
	}

	/**
	 * Creates a Profile object using a Cursor, the Cursor need to be at the
	 * position where the profile should be created and the cursor will not be
	 * changed or edited at all.
	 * 
	 * @param Cursor
	 *            c
	 * @return Profile
	 * @throws ParseException
	 */
	private Profile createProfile(final Cursor c) {

		Profile p = new Profile();

		p.email = c.getString(c.getColumnIndex(Profile.emailColumn));
		p.fname = c.getString(c.getColumnIndex(Profile.fnameColumn));
		p.homepage = c.getString(c.getColumnIndex(Profile.homepageColumn));
		p.lastUpdated = new Date(1000 * c.getLong(c.getColumnIndex(Profile.lastupdatedColumn)));
		p.lname = c.getString(c.getColumnIndex(Profile.lnameColumn));
		p.mobile = c.getString(c.getColumnIndex(Profile.mobileColumn));
		// p.phoneNumber = c.getString(c.getColumnIndex(Profile.p
		p.username = c.getString(c.getColumnIndex(Profile.usernameColumn));
		Log.e(TAG, "date in created profile " + p.username + " is: "
				+ p.lastUpdated.toString());
		return p;
	}

	private Profile createFullProfile(final Cursor c) {

		Profile p = this.createProfile(c);

		// add last updated footprint
		p.footprints = new FootPrint[1];
		FootPrint fp = new FootPrint();
		fp.latitude = c.getInt((c.getColumnIndex(FullProfile.latitudeColumn)));
		fp.longitude = c.getInt((c.getColumnIndex(FullProfile.longitudeColumn)));
		fp.timeStamp = new Date((c.getLong(c.getColumnIndex(FootPrint.timestampColumn))));
		// fp.statusId = c.getInt(c.getColumnIndex()
		p.footprints[0] = fp;
		// add external sys

		return p;
	}

	/**
	 * Writes the profile to database, used to update or create own profile only
	 * function that saves the password.
	 * 
	 * @param profile
	 */
	public void saveProfile(Profile profile) {
		dbAdapter.writeProfile(profile);
		wsClient.updateUserProfile(profile);
	}

	/**
	 * Get's the friendlist from the database.
	 * 
	 * @param userProfile
	 * @return
	 */
	public Cursor getFriendsCursorLocal(Profile userProfile) {
		return dbAdapter.fetchAllProfiles(Profile.AllColumns); // FullProfile
	}

	public Cursor getFriendsCursorAllDataLocal(String[] columns) {
		return dbAdapter.fetchAllProfilesAllData(columns);
	}

	/**
	 * Get's the friendlist from the database.
	 * 
	 * @param userProfile
	 * @return
	 */
	public ArrayList<Profile> getFriendsLocal(Profile userProfile) {
		ArrayList<Profile> profiles = new ArrayList<Profile>();
		Cursor c = dbAdapter.readBetaView(); // FullProfile
		boolean run = c.moveToFirst();
		while (run) {
			profiles.add(this.createFullProfile(c));
			run = c.moveToNext();
		}
		c.close();
		return profiles;
	}

	public void saveFootPrint(FootPrint fp, String username) {
		dbAdapter.saveFootprint(username, fp);
		Log.v(TAG,"Sending footprint to server");
		boolean success = wsClient.sendFootPrints(username, fp);
		Log.v(TAG,"Success: " + success);
	}

	/**
	 * Gets the updated list from the server, only the updates that have been
	 * recorded to the server since last time we got a record from the server.
	 * 
	 * @param userProfile
	 * @return updated full friends list
	 */
	public void getFriendsExternal(Profile userProfile) {
		ArrayList<Profile> updatedList = wsClient.getFriendList(userProfile, lastUpdated);
		if(updatedList != null) {
			lastUpdated = Calendar.getInstance().getTime();
		}												
		dbAdapter.updateProfile(updatedList); // save the updated friends
		ArrayList<ExternalData> list = getAllExternalData();
		try {
			saveExternalData(cc.getAllStatus(list));
		} catch (Exception e) {
			// Log.v(TAG)
		}
	}

	public Cursor getProfileCursor(String username) {
		return dbAdapter.readProfile(username);

	}

	private ExternalData createExternalData(Cursor c) {
		ExternalData ed = new ExternalData();
		ed.application = c.getInt(c
				.getColumnIndex(ExternalData.applicationColumn));
		ed.errorMsg = c
				.getString(c.getColumnIndex(ExternalData.errormsgColumn));
		ed.latestUpdate = new Date(1000 * c.getLong(c
				.getColumnIndex(ExternalData.latestUpdateColumn)));
		ed.password = c
				.getString(c.getColumnIndex(ExternalData.passwordColumn));
		ed.statusText = c.getString(c
				.getColumnIndex(ExternalData.statusTextColumn));
		ed.success = (c
				.getShort((c.getColumnIndex(ExternalData.successColumn))) == 1);
		ed.login = c.getString(c.getColumnIndex(ExternalData.loginColumn));
		ed.userId = c.getString(c.getColumnIndex(ExternalData.userIdColumn));
		ed.username = c
				.getString(c.getColumnIndex(ExternalData.usernameColumn));
		return ed;
	}

	/**
	 * logs in external data client
	 * 
	 * @param ed
	 * @return true if successful
	 * @throws IExternalClient
	 *             if unsuccessful
	 */
	public boolean loginExternalClient(ExternalData ed) throws IExternalClient {
		ProfileFactory.cc.loginClient(ed.application);
		return true;

	}

	/**
	 * used for logging in a collection of external datas... no checking if they
	 * succeed
	 * 
	 * @param eds
	 */
	public void loginExternalClient(ExternalData... eds) {
		for (ExternalData ed : eds)
			try {
				ProfileFactory.cc.loginClient(ed.application);
			} catch (Exception e) {
				Log.e(TAG, "could not login applicationId " + ed.application
						+ e.getMessage());
			}

	}

	/**
	 * returns a list of all external data objects
	 * 
	 * @return
	 */
	public ArrayList<ExternalData> getAllExternalData() {
		ArrayList<ExternalData> edList = new ArrayList<ExternalData>();
		Cursor c = dbAdapter.getAllExternals();

		while (c.moveToNext()) {
			ExternalData ed = createExternalData(c);
			edList.add(ed);
		}
		c.close();
		return edList;
	}

	/**
	 * gets the latest status from external data sources overwrite the status
	 * field in ExternalData object
	 * 
	 * @param ed
	 * @return
	 * @throws IExternalClient
	 */
	public ExternalData getStatus(ExternalData ed) throws IExternalClient {
		return cc.getStatus(ed);
	}

	/**
	 * gets latest statuses from external data sources overwrite the status
	 * field in ExternalData object
	 * 
	 * @param edList
	 * @return
	 * @throws FriendStalkerException
	 */
	public ExternalData[] getStatus(ArrayList<ExternalData> edList)
			throws FriendStalkerException {
		return cc.getAllStatus(edList);
	}

	/**
	 * saves external data object to local database
	 * 
	 * @param ed
	 * @throws IExternalClient 
	 */
	public void saveExternalData(ExternalData ed, Boolean internetAvailable) throws IExternalClient {
		dbAdapter.saveExternal(ed);
		
		
	}


	/**
	 * saves external data objects to local database
	 * 
	 * @param ed
	 */
	public void saveExternalData(ExternalData[] edList) {
		dbAdapter.saveExternals(edList);
	}

	public void changeLoginExternalClient(int appId, String username, String password) throws IExternalClient
	{
		cc.changeLoginExternalClient(appId, username, password);
	}
	public Object executeMethodExternalClient(int appId, String method, String ...parameters) throws FriendStalkerException.IExternalClient
	{
		return cc.executeMethodExternalClient(appId, method, parameters);
	}
}
